/* 
 * File:   SceneModel.cpp
 * Author: Nick
 * 
 * Created on 13 April 2011, 9:46 PM
 *
 * Resolution levels
 * 0 = 1000km
 * 1 = 100km
 * 2 = 10km
 * 3 = 1km
 * 4 = 100m
 * 5 = 10m
 * 6 = 1m
 * 7 = 10cm
 * 8 = 1cm (object positioning only)
 */

#include "Server.h"
#include "SceneModel.h"
#include "TerrainRenderer.h"
#include "Terrain.h"
#include "BlockRenderer.h"

SceneModel* primary_model = 0;

SceneModel::SceneModel() {
    _server = new Server(this);

    // Set some random point as the player world position.
    _root_coord[0] = 5; //X
    _root_coord[1] = 1; //Y
    _root_coord[2] = 5; //Z
    _root_coord[3] = 5; //X
    _root_coord[4] = 1; //Y
    _root_coord[5] = 5; //Z
    _root_coord[6] = 5; //X
    _root_coord[7] = 1; //Y
    _root_coord[8] = 5; //Z
    _root_coord[9] = 0; //X
    _root_coord[10] = 0; //Y
    _root_coord[11] = 0; //Z
    _root_coord[12] = 5; //X
    _root_coord[13] = 0; //Y
    _root_coord[14] = 0; //Z
    _root_coord[15] = 5; //X
    _root_coord[16] = 1; //Y
    _root_coord[17] = 1; //Z
    _root_coord[18] = 5; //X
    _root_coord[19] = 1; //Y
    _root_coord[20] = 0; //Z

    _version = 0;
    _first = 0;
    _pallet_size = 2;

    for (int i = 0; i < WORLD_DEPTH; i++) {
        roots[i] = 0;
    }

    _terrain_pallet = new TerrainDefinition*[_pallet_size];
    

    _terrain_pallet[0] = new TerrainDefinition(0);
    _terrain_pallet[0]->renderer = new NullRenderer();
    _terrain_pallet[1] = new TerrainDefinition(1);
    _terrain_pallet[1]->renderer = new CubeRenderer();
}

SceneModel::~SceneModel() {
    delete _server;
    for (int i = 0; i < _pallet_size; ++i) {
        delete _terrain_pallet[i];
    }
    for (int i = 0; i < WORLD_DEPTH; ++i) {
        delete roots[i];
    }
}

void  SceneModel::print_status(){
    for (int d = 0; d < 8; d++) {
         printf("Root: %d\n", d);
         printf("Node count: %d\n", roots[d]->count);
         int peers = 0;
         int total = roots[d]->count;
         Cell* b = roots[d];
         while(b->next != 0){
             b = static_cast<Cell*>(b->next);
             total += b->count;
             peers++;
         }
         printf("Node peers: %d\n", peers);
    }
}

void const SceneModel::get_root_coord(UNIT* copy) {
    memcpy(copy, _root_coord, 3 * WORLD_DEPTH);
}

int const SceneModel::get_version() {
    return _version;
}

void SceneModel::set_root(UNIT* new_coord_root) {
    for (int i = 0; i < 21; i++) {
        _root_coord[i] = new_coord_root[i];
    }
}

void SceneModel::load() {

    for (int d = 0; d < 7; d++) {
        _load(_root_coord, d, 0);
    }

    for (int d = 2; d < 7; d++) {
        UNIT rc[3*WORLD_DEPTH];
        get_root_coord(rc);
        int di = d * 3;
        int m = d;
        if(d == 6){
            m = m * 7;
        }
        for (int x = -VIEW_DISTANCE*m; x <= VIEW_DISTANCE*m; x++) {
            rc[di] = _root_coord[di]+x;
            for (int y = -VIEW_DISTANCE*d; y <= VIEW_DISTANCE*d; y++) {
                rc[di+1] = _root_coord[di+1]+y;
                for (int z = -VIEW_DISTANCE*m; z <= VIEW_DISTANCE*m; z++) {
                    if(x == 0 && y == 0 && z == 0) continue;
                    rc[di+2] = _root_coord[di+2]+z;
                    _load(rc, d, 0);
                }
            }
        }
    }
//    printf("SceneModel::load()\n");
}

void SceneModel::_load(UNIT* coord, UNIT coord_depth, UNIT lod) {
//    printf("Loading - (%d,%d,%d)\n", coord[coord_depth*3], coord[coord_depth*3+1], coord[coord_depth*3+2] );
    UNIT tempCoord[coord_depth*3];
    _normalise_coord(coord, tempCoord, coord_depth);
    _server->load_block(tempCoord, coord_depth, lod);
}

void SceneModel::add_block_to_scene(UNIT* coord, UNIT coord_depth, Block* block) {
//    printf("Adding to scene - (%d,%d,%d)\n", coord[coord_depth*3], coord[coord_depth*3+1], coord[coord_depth*3+2] );

    if (coord_depth < 0) {
        return;
    }

    block->terrain_def = get_terrain_def(block->terrain_def_id);
    block->set_pos(coord+coord_depth*3);
    
    if (coord_depth == 0) {
        if (block->get_type() != BLOCK_TYPE_CELL) return;
        roots[0] = static_cast<Cell*> (block);
        return;
    }

    if (roots[coord_depth - 1] == null) {
        return;
    }

//    if (coord_depth == 1) {
//        if (block->get_type() != BLOCK_TYPE_CELL) return;
//        roots[coord_depth - 1]->set_block(coord + coord_depth*3, block);
//        roots[coord_depth] = static_cast<Cell*> (block);
//        return;
//    }


    if (_is_root(coord, coord_depth)) {
        if (block->get_type() == BLOCK_TYPE_CELL){
//            printf("Adding to scene 2 -_is_root  (%d,%d,%d)\n", coord[coord_depth*3], coord[coord_depth*3+1], coord[coord_depth*3+2] );
            roots[coord_depth] = static_cast<Cell*> (block);
        }
    }

//    printf("Adding to scene 2 - (%d,%d,%d)\n", coord[coord_depth*3], coord[coord_depth*3+1], coord[coord_depth*3+2] );
    int d3 = coord_depth * 3;
    Cell* c = get_cell(coord, coord_depth - 1);
//    printf("Adding to scene 3 - (%d,%d,%d) - %d\n", coord[coord_depth*3], coord[coord_depth*3+1], coord[coord_depth*3+2], c );
    c->set_block(coord + d3, block);
//    printf("Adding to scene 4 - (%d,%d,%d)\n", coord[coord_depth*3], coord[coord_depth*3+1], coord[coord_depth*3+2] );
    
}

Cell* SceneModel::get_cell(UNIT* coord, UNIT coord_depth) {
    int cd = coord_depth;
    while (!_is_root(coord, cd)) {
        cd--;
    }

    Cell* tempCell = roots[cd];

    while (cd < coord_depth) {
        cd++;
        tempCell = tempCell->get_cell(coord + cd * 3);
    }


    return tempCell;
}

bool SceneModel::_is_root(UNIT* coord, UNIT coord_depth) {
    for (int i = 0; i < coord_depth * 3 + 3; i++) {
        if (coord[i] != _root_coord[i]) {
            return false;
        }
    }
    return true;
}

void SceneModel::_normalise_coord(UNIT* coord, UNIT* newCoord, UNIT coord_depth) {

    //Copy the values of the coordinates into the new coordinates
    //The size is according to depth, e.g. at depth 2, there are (2+1)*3 = 9 coordinates
    //quick table: 0=3, 1=6, 2=9, 3=12, 4=15, 5=18, 6=21, 7=24, 8=27, 9=30
    memcpy(newCoord, coord, (coord_depth + 1)*3);
    //d3 is a shortcut for 3 times the depth coord, used as offset to dirst value for the depth
    //bound is the max size for each dimension of the cell
    int d3, bound;
    for (int d = coord_depth; d >= 0; d--) {
        d3 = d * 3;
        //For each of x, y, z coords
        for (int i = 0; i < 3; i++) {
            bound = CELL_WHD[i];
            if (newCoord[d3 + i] > bound) {
                newCoord[d3 + i - 3] += newCoord[d3 + i] / bound;
                newCoord[d3 + i] = coord[d3 + i] % bound;
            } else if (newCoord[d3 + i] < 0) {
                newCoord[d3 + i - 3] -= (-newCoord[d3 + i] / bound) + 1;
                newCoord[d3 + i] = 10 - (-newCoord[d3 + i] % bound);
            }
        }
    }
    _first++;

    //return newCoord;
}

TerrainDefinition* SceneModel::get_terrain_def(int id) {
    for (int i = 0; i < _pallet_size; i++) {
        if (_terrain_pallet[i]->id == id) {
            return _terrain_pallet[i];
        }
    }
    return _terrain_pallet[0];
}